﻿using Advantech.Adam;
using Advantech.Common;
using DryIoc;
using EONFULLSIN2.Core;
using EONFULLSIN2.Core.Extensions;
using EONFULLSIN2.Models.Attributes;
using EONFULLSIN2.Models.Dto.Device;
using EONFULLSIN2.Models.Enum.IOPointEnum;
using ImTools;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EONFULLSIN2.Services.Device
{
    /// <summary>
    /// 通用AdamComBaseServices实现接口
    /// </summary>

    public abstract class AdamComBaseServices : IAdamComBaseServices
    {
        #region Fields
        protected AdamCom adamCom;

        /// <summary>
        /// 广播地址
        /// </summary>
        protected int m_iAddr;
        /// <summary>
        /// 读取次数
        /// </summary>
        protected int m_iCount;

        /// <summary>
        /// DI通道数量
        /// </summary>
        protected int m_iDITotal;

        /// <summary>
        /// DO通道数量
        /// </summary>
        protected int m_iDOTotal;

        /// <summary>
        /// 开启标志
        /// </summary>
        protected bool m_bStart;

        /// <summary>
        /// ADAM类型
        /// </summary>
        protected Adam4000Type m_Adam4000Type;

        /// <summary>
        /// ADAM配置
        /// </summary>
        protected Adam4000Config m_adamConfig;

        /// <summary>
        /// 取消Task令牌
        /// </summary>
        protected readonly CancellationTokenSource readTaskCts;

        /// <summary>
        /// IO_StatusResult
        /// </summary>
        protected readonly ConcurrentBag<RelayChannelItemDto> dIOStatusResult;
        #endregion

        protected AdamComBaseServices()
        {
            m_iAddr = 1; // the slave address is 1
            m_iCount = 0; // the counting start from 0
            m_bStart = false;
            m_Adam4000Type = Adam4000Type.Adam4068;
            m_iDITotal = DigitalInput.GetChannelTotal(m_Adam4000Type);
            m_iDOTotal = DigitalOutput.GetChannelTotal(m_Adam4000Type);

            readTaskCts = new CancellationTokenSource();
            readTaskCts.Token.Register(() =>
            {
                LogManager.Info($"{COMName},The task of reading Adam relay status has been canceled");
            });
            dIOStatusResult = new ConcurrentBag<RelayChannelItemDto>();
        }


        public virtual string COMName { protected set; get; }

        public virtual List<eIOPointEnum> AdamIOPoints { protected set; get; }

        public bool IsOpen => adamCom == null ? false : adamCom.IsOpen;


        #region Methods
        
        public async Task<bool> OpenComAsync()
        {
            var task = await Task.Factory.StartNew(() =>
            {
                try
                {
                    if (adamCom == null && IsOpen == false)
                    {
                        adamCom = new AdamCom(COMName);
                        adamCom.Checksum = false; // disabled checksum
                        if (adamCom.OpenComPort())
                        {
                            // set COM port state, 9600,N,8,1
                            adamCom.SetComPortState(Baudrate.Baud_9600, Databits.Eight, Parity.None, Stopbits.One);
                            // set COM port timeout
                            adamCom.SetComPortTimeout(200, 1000, 0, 1000, 0);
                            m_iCount = 0; // reset the reading counter
                                          // get module config
                            if (!adamCom.Configuration(m_iAddr).GetModuleConfig(out m_adamConfig))
                            {
                                adamCom.CloseComPort();
                                LogManager.Error($"{COMName},Failed to get module config!");
                                //MessageBox.Show("Failed to get module config!", "Error");
                                return false;
                            }
                            m_bStart = true; // starting flag
                            return true;
                        }
                        else
                        {
                            //MessageBox.Show("Failed to open COM port!", "Error");
                            LogManager.Error($"{COMName},Failed to open COM port");
                            return false;
                        }
                    }
                    return true;
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException($"{COMName},An error occurred while opening the COM port", ex);   
                }

            });

            return task;
        }

        public async Task<bool> CloseComAsync()
        {
            var task = await Task.Run(() =>
            {
                try
                {
                    if (adamCom != null && adamCom.IsOpen)
                    {
                        // 取消任务
                        readTaskCts?.Cancel();
                        adamCom.CloseComPort();
                    }
                    else
                    {
                        return false;
                    }
                    return adamCom.IsOpen;
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException($"{COMName},An exception occurred during task execution", ex);
                }
            });

            return task;
        }

        public async Task StartReadDOChannelStatusAsync()
        {
            try
            {
                if (adamCom != null && adamCom.IsOpen)
                {
                    while (!readTaskCts.IsCancellationRequested)
                    {
                        dIOStatusResult?.Clear();
                        m_iCount++;
                        var readRlt = await ReadDOAllChannelStatusAsync();
                        Debug.WriteLine($"读取次数：{m_iCount}，结果：{JsonConvert.SerializeObject(readRlt)}");
                        if (readRlt.DigitalInputRlt)
                        {
                            foreach (var item in readRlt.ChannelStatusList)
                            {
                                dIOStatusResult?.Add(item);
                            }
                        }
                        await Task.Delay(50);//每间隔50ms获取一次
                    }
                }
                else
                {
                    throw new InvalidOperationException($"{COMName},port is not open");
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"{COMName},An exception occurred during task execution", ex);
            }
        }

        public async Task<bool> StopReadDOChannelStatusAsync()
        {
            try
            {
                await readTaskCts?.CancelAsync();
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"{COMName},An error occurred while canceling the task", ex);
            }
            
            return true;
        }

        public virtual async Task<List<SensorItemDto>> GetDOChannelStatusAsync() {
            //List<SensorItemDto> sensorItemDtos = new List<SensorItemDto>();
            //foreach (var item in dIOStatusResult) {
            //    var adamDisplayAttribute = item.AdamIOPointType.GetAttributeOfType<AdamPointAttribute>();
            //    SensorItemDto sensorItemDto = new SensorItemDto() {
            //        ID = item.ChannelIndex,
            //        Name = $"{adamDisplayAttribute.Description}",
            //        CodeNumber = $"{adamDisplayAttribute.Name}",
            //        Type = Models.Enum.eSensorTypeEnum.IOMdoel,
            //        Value = item.ChannelStatus,
            //        DataSourceAddress = $"{COMName}_{item.ChannelIndex}"
            //    };
            //    sensorItemDtos.Add(sensorItemDto);
            //}
            //return await Task.FromResult(sensorItemDtos);

            List<SensorItemDto> sensorItemDtos = new List<SensorItemDto>();
            foreach (var item in AdamIOPoints) {
                var adamDisplayAttribute = item.GetAttributeOfType<IOPointAttribute>();
                var value = dIOStatusResult.FirstOrDefault(o => o.IOPointType == item);
                SensorItemDto sensorItemDto = new SensorItemDto() {
                    CodeNumber = $"{adamDisplayAttribute.Name}",
                    Name = $"{adamDisplayAttribute.Description}",
                    Type = Models.Enum.eSensorTypeEnum.IOMdoel,
                    Value = value?.ChannelStatus,
                    DataSourceAddress = $"{adamDisplayAttribute.Name}-{adamDisplayAttribute.ChannelName}",
                    IsEnable = adamDisplayAttribute.IsEnable
                };
                sensorItemDtos.Add(sensorItemDto);
            }
            return await Task.FromResult(sensorItemDtos);
        }

        public virtual Task<(bool DigitalInputRlt, List<RelayChannelItemDto> ChannelStatusList)> ReadDOAllChannelStatusAsync()
        {
            throw new NotImplementedException();
        }


        public Task<(bool DigitalInputRlt, bool ChannelStatus)> ReadDOChannelStatusAsync(int i_iCh)
        {
            (bool digitalInputRlt, bool channelStatus) res = (false, false);
            bool[] bDI, bDO;
            var readRes = adamCom.DigitalInput(m_iAddr).GetValues(m_iDITotal, m_iDOTotal, out bDI, out bDO);
            res.digitalInputRlt = readRes;
            if (readRes)
            {
                res.channelStatus = bDO[i_iCh];
            }
            return Task.FromResult(res);
        }


        public async Task<bool> SetDOChannelStatusAsync(int i_iCh, bool value)
        {
            bool bRet;
            if (m_iDITotal > 0)
            {
                i_iCh = i_iCh - 8;
            }
            if (m_iDOTotal > 8)
                bRet = adamCom.DigitalOutput(m_iAddr).SetSValue(i_iCh, value);
            else
                bRet = adamCom.DigitalOutput(m_iAddr).SetValue(i_iCh, value);
            //if (!bRet)
            //    return false;
            return await Task.FromResult(bRet);
        }

        #endregion
    }
}
